package net.pimpas.persistence.hibernate.configuration;

import java.util.ArrayList;
import java.util.List;

import net.pimpas.core.configuration.Configuration;
import net.pimpas.core.configuration.exception.ConfigurationException;
import net.pimpas.core.configuration.file.FileConfiguration;
import net.pimpas.logging.serviceprovider.LoggingServiceProvider;
import net.pimpas.logging.serviceprovider.LoggingServiceProviderFactoryImpl;
import net.pimpas.xml.exception.XmlAccessException;
import net.pimpas.xml.exception.XmlParseException;
import net.pimpas.xml.node.Node;
import net.pimpas.xml.serviceprovider.XmlServiceProvider;
import net.pimpas.xml.serviceprovider.XmlServiceProviderFactoryImpl;

import org.hibernate.Interceptor;
import org.hibernate.tool.hbm2ddl.SchemaExport;
import org.hibernate.tool.hbm2ddl.SchemaUpdate;

public class HibernateConfiguration implements Configuration<FileConfiguration> {
	private final static LoggingServiceProvider logger = LoggingServiceProviderFactoryImpl.getLoggingServiceProvider(HibernateConfiguration.class);

	private List<FileConfiguration> filesConfiguration;
	private org.hibernate.cfg.Configuration hibernateConfiguration;

	public HibernateConfiguration(org.hibernate.cfg.Configuration hibernateConfiguration) {
		this.setFilesConfiguration(new ArrayList<FileConfiguration>());
		this.setHibernateConfiguration(hibernateConfiguration);
	}

	public String getName() {
		return this.getClass().getSimpleName();
	}

	public void consume() throws ConfigurationException {
		if(this.getFilesConfiguration().size() == 0) {
			logger.error("Impossible configure "+this.getName()+", no FileConfiguration" +
			" found");
			return;
		}

		// choice one configuration
		// its a simple test, lets take the first one
		FileConfiguration fc = this.getFilesConfiguration().get(0);
		logger.debug("Configuring using FileConfiguration: "+fc.getFileUrl().getFile());
		XmlServiceProvider xmlReader = XmlServiceProviderFactoryImpl.getXmlServiceProvider();
		Class interceptor = null;
		try {
			xmlReader.parse(fc.getInputStream());
			List<Node> nodes = xmlReader.getChildrens("connection");
			Node connection = null;
			for(int i=0;i<nodes.size();i++) {
				// look for default one
				if(xmlReader.getAttribute(nodes.get(i), "default") != null &&
						xmlReader.getAttribute(nodes.get(i), "default").getText().equals("true")) {
					// here, initialize and stop
					logger.debug("Default Connection found... initializing");
					connection = nodes.get(i);
					logger.debug("Default Connection Label: "+xmlReader.getAttribute(connection, "label").getText());
					break;
				}
			}

			if(connection == null) { // get the first one
				connection = nodes.get(0);
				logger.debug("Can't find default connection, taking the first one!");
			}

			if(connection == null) {
				logger.fatal("Can't find connection for hibernate!");
				throw new ConfigurationException("Can't find connection for hibernate!");
			}

			// init hibernate
			List<Node> properties = xmlReader.getChildrens(connection, "property");
			String name,value = null;
			String pimpasSchema = null;
			for(int i=0;i<properties.size();i++) {
				name = xmlReader.getAttribute(properties.get(i), "name").getText();
				value = properties.get(i).getText();
				if(name.equals("pimpas.persistence.interceptor")) {
					try {
						interceptor = Class.forName(value);
					} catch(Exception e) {
						throw new ConfigurationException("Cannot find Interceptor Class: "+value);
					}
				}
				else {
					if(name.equals("pimpas.persistence.schema"))
						pimpasSchema = value;
					else if(name.equals("hibernate.improved_naming_strategy")) {
							try {
								hibernateConfiguration.setNamingStrategy((org.hibernate.cfg.ImprovedNamingStrategy) Class.forName(value).newInstance());
							}
							catch (Exception e) {
							}
					} else {
						logger.debug("[Setting property] name: "+name+", value: "+value);
						this.getHibernateConfiguration().setProperty(name, value);
					}
				}
			}
			if(interceptor != null)
				try {
					hibernateConfiguration.setInterceptor((Interceptor) interceptor.newInstance());
					logger.info("Hibernate Interceptor Configured, class: "+interceptor.getName());
				} catch(Exception e) {
					throw new ConfigurationException("Cannot instantiate interceptor class: "+interceptor.getName());
				}


				nodes = xmlReader.getChildrens("configurations");
				Node file = null;
				logger.debug("Looking for hbm files...");
				for(Node node: nodes) {
					file = xmlReader.getFirstNode(node, "file");
					this.getHibernateConfiguration().addResource(file.getText());
				}

				logger.debug("Exporting Schema...");
				if(pimpasSchema != null && pimpasSchema.equals("update"))
					new SchemaUpdate(hibernateConfiguration).execute(false, true);
				else if(pimpasSchema != null && pimpasSchema.equals("export"))
					new SchemaExport(hibernateConfiguration).create(false, true);
				logger.debug("Hibernate configured!");
		} catch (XmlParseException e) {
			logger.error("Exception when trying parse XML", e);
			throw new ConfigurationException(e);
		} catch (XmlAccessException e) {
			logger.error("Exceptin when trying access XML Nodes", e);
			throw new ConfigurationException(e);
		}
	}

	public List<FileConfiguration> getFilesConfiguration() {
		return this.filesConfiguration;
	}

	private void setFilesConfiguration(List<FileConfiguration> filesConfiguration) {
		this.filesConfiguration = filesConfiguration;
	}

	private org.hibernate.cfg.Configuration getHibernateConfiguration() {
		return hibernateConfiguration;
	}

	private void setHibernateConfiguration(
			org.hibernate.cfg.Configuration hibernateConfiguration) {
		this.hibernateConfiguration = hibernateConfiguration;
	}

	public List<FileConfiguration> getConfigurationSources() {
		return this.getFilesConfiguration();
	}
}